/*******************************************************************************************
 *      @name   ai_model.c
 *      @brief  这是在linux系统下使用HTTP协议向AI大模型发送请求的代码案例
 *      @date   2025/02/22
 *      @author boluzs@163.com
 *      @note
 *
 *
 *      @version  xx.xx.xx   主版本号.次版本号.修正版本号
 *
 *      CopyRight (c)   2024-2025   Tokisaki     All Right Reserved
 *
 * ****************************************************************************************/

#include "cJSON.h"
#include <arpa/inet.h>
#include <dirent.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#define MAIN_DEBUG 0
#define BUFSIZE    1024

#define HTTP_PORT  80

#define AI_API_KEY \
    "bce-v3/ALTAK-wSsiLKZuj8CRHQ03A8oVI/c7aacb0e99b40fda6b0da81ad10f2dbd863cfaeb"
#define AI_DOMAIN "aip.baidubce.com"
#define AI_REQURL "/rest/2.0/image-classify/v1/image-understanding/request"
#define AI_GETURL "/rest/2.0/image-classify/v1/image-understanding/get-result"
#define AI_MODEL  "doubao-1-5-lite-32k-250115"

long filesize = 0;

// 注意：传入的字节大小可能会超过char型所以应当使用unsigned char型
unsigned char *binary2base64(unsigned char *array, int len)
{
    if (array == NULL || len == 0)
        return NULL;

    char base64[64] = {
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"};

    // base64编码后的字节数必须是4的倍数(多加一个字节是为了预留填写'\0'空间)
    printf("\nlen = %d\n", len);
    // 如果要编码的字节数的余数不够3个字节，则需多4个字节的空间来对1或2个字节进行编码后补'='号
    unsigned char *buffer =
        ((len % 3 == 0) ? (unsigned char *)calloc(1, len / 3 * 4 + 1)
                        : (unsigned char *)calloc(1, len / 3 * 4 + 4 + 1));
    if (buffer == NULL)
    {
        fprintf(stderr, "calloc error, %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    int i = 0;

    // 要注意的是，array的跨度是3个字节，转换后的buffer接收的跨度是4个字节
    for (i = 0; i < len / 3; i++)
    {
        buffer[0 + i * 4] = base64[array[0 + i * 3] >> 2];

        buffer[1 + i * 4] =
            base64[((array[0 + i * 3] & 0x03) << 4) | (array[1 + i * 3] >> 4)];

        buffer[2 + i * 4] =
            base64[((array[1 + i * 3] & 0x0F) << 2) | (array[2 + i * 3] >> 6)];

        buffer[3 + i * 4] = base64[array[2 + i * 3] & 0x3F];
    }

    // 特殊情况
    if (len % 3 == 1)
    {
        buffer[0 + i * 4] = base64[array[0 + i * 3] >> 2];

        buffer[1 + i * 4] = base64[(array[0 + i * 3] & 0x03) << 4];
    }

    if (len % 3 == 2)
    {
        buffer[0 + i * 4] = base64[array[0 + i * 3] >> 2];

        buffer[1 + i * 4] =
            base64[((array[0 + i * 3] & 0x03) << 4) | (array[1 + i * 3] >> 4)];

        buffer[2 + i * 4] = base64[(array[1 + i * 3] & 0x0F) << 2];
    }

    // 需要补'='号的情况
    if (len % 3 != 0)
    {
        for (int i = 0; i < 3 - len % 3; i++)
        {
            // 进行-1是因为要给数组预留'\0'的空间
            buffer[len / 3 * 4 + 4 - 1 - i] = '=';
        }
    }

    return buffer;
}

unsigned char *getFileByte(char *fileName)
{
    FILE *fp = fopen(fileName, "rb");
    if (fp == NULL)
    {
        fprintf(stderr, "fopen %s error, %s\n", fileName, strerror(errno));
        exit(EXIT_FAILURE);
    }

    fseek(fp, 0, SEEK_END);
    filesize = ftell(fp);

    unsigned char *buffer = (unsigned char *)calloc(1, filesize);
    if (buffer == NULL)
    {
        fprintf(stderr, "calloc buffer error, %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    fseek(fp, 0, SEEK_SET);
    fread(buffer, filesize, 1, fp);

    fclose(fp);

    return buffer;
}

int main(int argc, char const *argv[])
{
    // 根据域名获取主机信息
    struct hostent *phost = gethostbyname(AI_DOMAIN);

#if MAIN_DEBUG
    // 获取备用域名
    int cnt = 0;
    while (phost->h_aliases[cnt])
    {
        printf("[%s] h_aliases[%d] is [%s]\n", AI_DOMAIN, cnt, phost->h_aliases[cnt]);
        cnt++;
    }

    // 获取IP地址
    cnt = 0;
    while (phost->h_addr_list[cnt])
    {
        printf("[%s] h_addr_list[%d] is [%s]\n",
               AI_DOMAIN,
               cnt,
               inet_ntoa(*(struct in_addr *)(phost->h_addr_list[cnt])));
        cnt++;
    }
#endif

    // 主线程对HTTP服务器发送连接申请--首先创建TCP套接字
    int ai_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (ai_socket == -1)
    {
        fprintf(stderr, "ai_socket error, %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    // 利用connect函数进行连接--首先要有类的实例
    struct sockaddr_in http = {0};
    http.sin_family         = AF_INET;          // 族群为IPv4族
    http.sin_port           = htons(HTTP_PORT); // 目标端口--需要网络字节序
    http.sin_addr.s_addr    = inet_addr(inet_ntoa(
        *(struct in_addr *)(phost->h_addr_list[0]))); // 目标地址--需要网络字节序

    if (connect(ai_socket, (struct sockaddr *)&http, (socklen_t)sizeof(http)) == -1)
    {
        fprintf(stderr, "connect AI error, %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    printf("connect http to AI Success\n");

    // 申请1Mb大小堆内存
    char *senddata = (char *)calloc(1, BUFSIZE * 1024);
    // 申请4kb大小堆内存
    char *recvdata = (char *)calloc(1, BUFSIZE * 4);
    // 请求体申请512kb大小的堆内存
    char *body = (char *)calloc(1, BUFSIZE * 512);
    if (senddata == NULL || recvdata == NULL || body == NULL)
    {
        fprintf(stderr, "senddata or recvdata calloc error, %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    char msg[BUFSIZE / 4] = {0};
    char *pstr_set        = NULL;
    char *pstr_end        = NULL;

    int len = 0;

    unsigned char *file = getFileByte("0.jpg");

#if MAIN_DEBUG
    printf("前几个字节: ");
    for (int i = 0; i < 16 && i < filesize; i++)
    {
        printf("%02X ", file[i]);
    }
    printf("\n");
#endif

    unsigned char *base64code = binary2base64(file, filesize);

    for (;;)
    {
        printf("please Input your question: ");
        fgets(msg, sizeof(msg) - 1, stdin);

        len      = strcspn(msg, "\n");
        msg[len] = '\0';

        // 构造请求包体
        memset(body, 0, BUFSIZE * 4);
        sprintf(body, "{\"image\":\"%s\",\"question\":\"%s\"}", base64code, msg);

        // 构造请求
        memset(senddata, 0, BUFSIZE * 1024);
        sprintf(senddata,
                "POST %s HTTP/1.1\r\n"
                "Host: %s\r\n"
                "Content-Length: %ld\r\n"
                "Content-Type: application/json\r\n"
                "Authorization: Bearer %s\r\n"
                "\r\n"
                "%s",
                AI_REQURL,
                AI_DOMAIN,
                strlen(body),
                AI_API_KEY,
                body);

#if MAIN_DEBUG
        printf("\n%s\n", senddata);
#endif

        // 发送请求--第一次--提交请求
        send(ai_socket, senddata, strlen(senddata), 0);
        printf("send OK\n");

        // 接收响应--第一次
        sleep(1); // 延时确保响应包体的完整
        memset(recvdata, 0, BUFSIZE * 4);
        recv(ai_socket, recvdata, BUFSIZE * 4, 0);

#if MAIN_DEBUG
        printf("\n%s\n", recvdata);
#endif

        // 处理jSON数据
        pstr_set        = strstr(recvdata, "{"); // 此处也可以使用strchr
        pstr_end        = strrchr(pstr_set, '}');
        *(pstr_end + 1) = '\0';

        cJSON *root = cJSON_Parse(pstr_set);
        if (root == NULL)
        {
            fprintf(stderr, "cJSON_Parse error, %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
        printf("%s\n", cJSON_Print(root));

        cJSON *result = cJSON_GetObjectItem(root, "result");
        if (result == NULL)
        {
            fprintf(stderr, "cJSON_GetObjectItem result error, %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }

        cJSON *task_id = cJSON_GetObjectItem(result, "task_id");
        if (task_id == NULL)
        {
            fprintf(stderr, "cJSON_GetObjectItem task_id error, %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }

        memset(pstr_set, 0, strlen(pstr_set));
        pstr_set = cJSON_GetStringValue(task_id);

        // 构造请求包体--第二次--获取结果
        memset(body, 0, BUFSIZE * 512);
        sprintf(body, "{\"task_id\":\"%s\"}", pstr_set);

        // 构造请求--第二次--获取结果
        memset(senddata, 0, BUFSIZE * 1024);
        sprintf(senddata,
                "POST %s HTTP/1.1\r\n"
                "Host: %s\r\n"
                "Content-Length: %ld\r\n"
                "Content-Type: application/json\r\n"
                "Authorization: Bearer %s\r\n"
                "\r\n"
                "%s",
                AI_GETURL,
                AI_DOMAIN,
                strlen(body),
                AI_API_KEY,
                body);

#if MAIN_DEBUG
        printf("%s\n", senddata);
#endif

        // 循环等待AI进行处理
        for (;;)
        {
            // 发送请求--第二次--获取结果
            send(ai_socket, senddata, strlen(senddata), 0);
            printf("send OK\n");

            // 接收响应--第二次--获取结果
            sleep(1); // 延时确保响应包体的完整
            memset(recvdata, 0, BUFSIZE * 4);
            recv(ai_socket, recvdata, BUFSIZE * 4, 0);

#if MAIN_DEBUG
            printf("recvdata = %s\n", recvdata);
#endif

            if (strstr(recvdata, "processing") == NULL)
                break;
        }

        // 处理jSON数据
        pstr_set        = strstr(recvdata, "{"); // 此处也可以使用strchr
        pstr_end        = strrchr(pstr_set, '}');
        *(pstr_end + 1) = '\0';

        root = cJSON_Parse(pstr_set);
        if (root == NULL)
        {
            fprintf(stderr, "cJSON_Parse error, %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }

        result = cJSON_GetObjectItem(root, "result");
        if (root == NULL)
        {
            fprintf(stderr, "cJSON_GetObjectItem result error, %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }

        cJSON *description = cJSON_GetObjectItem(result, "description");
        if (description == NULL)
        {
            fprintf(
                stderr, "cJSON_GetObjectItem description error, %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }

        pstr_set = cJSON_GetStringValue(description);

        printf("%s\n", pstr_set);

        cJSON_Delete(root);
    }

    free(file);
    free(body);
    free(base64code);
    free(senddata);
    free(recvdata);

    close(ai_socket);

    return 0;
}
